/*###########################################################
# © 2013-2016 Daniel 'grindhold' Brendle
#
# This file is part of Nostril.
#
# Nostril is free software: you can redistribute it and/or 
# modify it under the terms of the GNU Affero General Public License 
# as published by the Free Software Foundation, either 
# version 3 of the License, or (at your option) any later 
# version.
#
# Nostril is distributed in the hope that it will be 
# useful, but WITHOUT ANY WARRANTY; without even the implied 
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE. See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public 
# License along with Nostril. 
# If not, see http://www.gnu.org/licenses/.
###########################################################*/

using Gee;
using Soup;
using ZLib.Utility;

namespace nostril {
    namespace data {
        public class Message : Object {
            private HashMap<string,string> _request_headers = new HashMap<string,string>();
            public HashMap<string,string> request_headers { get {return this._request_headers;}}
            public string request_body {get;set;default="";}

            private HashMap<string,string> _response_headers = new HashMap<string,string>();
            public HashMap<string,string> response_headers {get {return this._response_headers;}}
            public string response_body {get;set;default="";}

            public string hostname {get;set;default="";}
            public string path {get;set;default="";}
            public uint status_code {get;set;default=0;}
            public int64 size {get;set;default=0;}

            public Message() {}
        }

        public errordomain MessageStoreException {
            NO_MESSAGE_WITH_ID
        }

        private enum Decompression {
            GIZP
        }

        public class MessageStore : Object {
            private HashMap<int, nostril.data.Message> messages = new HashMap<int, nostril.data.Message>();
            private ArrayList<int> interesting_message_ids = new ArrayList<int>();
            private int id_gen = 0;

            private bool recording = true;

            public const int NOT_RECORDING = -1;

            public MessageStore(){}

            public int store_message(Soup.Message m, uint8[] data){
                if (this.recording){
                    var store_message = new nostril.data.Message();
                    m.request_headers.foreach((name, val) => {
                        store_message.request_headers.set(name,val);
                    });
                    store_message.request_body = (string) data;
                    store_message.hostname = m.uri.get_host();
                    store_message.path = m.uri.get_path();
                    

                    int last_id = this.id_gen++;
                    this.messages.set(last_id, store_message);
                    this.interesting_message_ids.add(last_id);
                    this.changed();
                    return last_id;
                } else {
                    return MessageStore.NOT_RECORDING;   
                }
            }

            public void complete_message(int id, Soup.Message m, uint8[] data ){
                // Ignore messages that finished after call of clear() or if message was not recorded
                if (!(this.messages.has_key(id)) || id == MessageStore.NOT_RECORDING){
                    return;
                }
                var store_message = this.messages[id];
                stdout.printf("OHAIO bENIS!\n");
                string dec = "none";
                m.response_headers.foreach((name, val) => {
                    store_message.response_headers.set(name,val);
                    if (name.down() == "content-encoding") {
                        dec = val;
                    }
                });
                
                // Decompress payload if compressed
                uint8[] decompressed;
                stdout.printf("payload encoding: %s\n",dec);
                switch (dec) {
                    case "gzip":
                        stdout.printf("gzip\n");
                        decompressed = GZip.uncompress(data);
                        break;
                    default:
                        //Unknown compression or simply not compressed
                        decompressed = data;
                        break;
                }

                store_message.response_body = (string) decompressed;
                store_message.size = m.response_body.length; 
                store_message.status_code = m.status_code;

                this.interesting_message_ids.add(id);
                this.changed();
                return;
            }

            public void reset_interesting_messages(){
                this.interesting_message_ids.clear();
            }

            public Iterator<int> get_interesting_messages(){
                return this.interesting_message_ids.iterator();
            }

            public void clear_messages(ArrayList<int> ids){
                foreach (int i in ids){
                    this.messages.unset(i);
                    this.interesting_message_ids.add(i);
                }
                this.changed();
            }

            public void clear(){
                MapIterator<int, nostril.data.Message> iter = this.messages.map_iterator();
                while (iter.next()){
                    this.interesting_message_ids.add(iter.get_key());
                }
                this.messages.clear();
                this.changed();
            }

            public void set_recording(bool state){
                this.recording = state;
            }

            public nostril.data.Message get_message(int id) throws MessageStoreException.NO_MESSAGE_WITH_ID{
                if (!(this.messages.has_key(id)))
                    throw new MessageStoreException.NO_MESSAGE_WITH_ID("This message is not in the MessageStore anymore");
                var msg =  this.messages[id] as nostril.data.Message;
                return msg; //Try with this debugging
            }

            public int get_message_count(){
                int s = this.messages.size;
                return s;
            }

        }
        class GZip : Object {
            const int CHUNKSIZE = 4096;
            public static uint8[] uncompress(uint8[] data) {
                stdout.printf("decompressing gzip\n");
                uint8[] dest = new uint8[0];
                uint32 multiplicator = 1;
                ulong size = 0;
                int res = -127;
                while (res != 0){
                    size = multiplicator * GZip.CHUNKSIZE;
                    dest = new uint8[size];
                    res = ZLib.Utility.uncompress(dest, ref size, data);
                    if ( res == -2 ) {
                        stdout.printf("memory overload while decompressing");
                        break;
                    }
                    multiplicator++;
                stdout.printf("Tried with multiplicator %u and size %l returned %d\n", multiplicator, size, res);
                }
                stdout.printf("finished decompressing gzip\n");
                return dest;
            }
        }

    }
}
